﻿using Grand.Business.Core.Interfaces.Common.Directory;
using Grand.Data;
using Grand.Domain;
using Grand.Domain.Customers;
using Grand.Infrastructure.Caching;
using Grand.Infrastructure.Caching.Constants;
using Grand.Infrastructure.Extensions;
using Grand.SharedKernel;
using MediatR;

namespace Grand.Business.Common.Services.Directory;

public class GroupService : IGroupService
{
    private readonly ICacheBase _cacheBase;
    private readonly IRepository<CustomerGroup> _customerGroupRepository;
    private readonly IMediator _mediator;

    public GroupService(IRepository<CustomerGroup> customerGroupRepository,
        ICacheBase cacheBase,
        IMediator mediator)
    {
        _customerGroupRepository = customerGroupRepository;
        _cacheBase = cacheBase;
        _mediator = mediator;
    }

    /// <summary>
    ///     Gets a customer group
    /// </summary>
    /// <param name="customerGroupId">Customer group identifier</param>
    /// <returns>Customer group</returns>
    public virtual Task<CustomerGroup> GetCustomerGroupById(string customerGroupId)
    {
        if (string.IsNullOrWhiteSpace(customerGroupId))
            return Task.FromResult<CustomerGroup>(null);

        var key = string.Format(CacheKey.CUSTOMERGROUPS_BY_KEY, customerGroupId);
        return _cacheBase.GetAsync(key, () => _customerGroupRepository.GetByIdAsync(customerGroupId));
    }

    /// <summary>
    ///     Gets a customer group
    /// </summary>
    /// <param name="systemName">Customer group system name</param>
    /// <returns>Customer group</returns>
    public virtual async Task<CustomerGroup> GetCustomerGroupBySystemName(string systemName)
    {
        var key = string.Format(CacheKey.CUSTOMERGROUPS_BY_SYSTEMNAME_KEY, systemName);
        return await _cacheBase.GetAsync(key, async () =>
        {
            return await Task.FromResult(
                _customerGroupRepository.Table.FirstOrDefault(x => x.SystemName == systemName));
        });
    }

    /// <summary>
    ///     Gets all customer groups
    /// </summary>
    /// <param name="pageSize"></param>
    /// <param name="showHidden">A value indicating whether to show hidden records</param>
    /// <param name="name"></param>
    /// <param name="pageIndex"></param>
    /// <returns>Customer groups</returns>
    public virtual async Task<IPagedList<CustomerGroup>> GetAllCustomerGroups(string name = "", int pageIndex = 0,
        int pageSize = int.MaxValue, bool showHidden = false)
    {
        var query = from m in _customerGroupRepository.Table
            select m;

        if (!showHidden)
            query = query.Where(m => m.Active);

        if (!string.IsNullOrEmpty(name))
            query = query.Where(x => x.Name.ToLowerInvariant().Contains(name.ToLowerInvariant()));

        query = query.OrderBy(m => m.DisplayOrder).ThenBy(m => m.Name);

        return await PagedList<CustomerGroup>.Create(query, pageIndex, pageSize);
    }

    /// <summary>
    ///     Inserts a customer group
    /// </summary>
    /// <param name="customerGroup">Customer group</param>
    public virtual async Task InsertCustomerGroup(CustomerGroup customerGroup)
    {
        ArgumentNullException.ThrowIfNull(customerGroup);

        await _customerGroupRepository.InsertAsync(customerGroup);

        await _cacheBase.RemoveByPrefix(CacheKey.CUSTOMERGROUPS_PATTERN_KEY);

        //event notification
        await _mediator.EntityInserted(customerGroup);
    }

    /// <summary>
    ///     Updates the customer group
    /// </summary>
    /// <param name="customerGroup">Customer group</param>
    public virtual async Task UpdateCustomerGroup(CustomerGroup customerGroup)
    {
        ArgumentNullException.ThrowIfNull(customerGroup);

        await _customerGroupRepository.UpdateAsync(customerGroup);

        await _cacheBase.RemoveByPrefix(CacheKey.CUSTOMERGROUPS_PATTERN_KEY);

        //event notification
        await _mediator.EntityUpdated(customerGroup);
    }

    /// <summary>
    ///     Delete a customer group
    /// </summary>
    /// <param name="customerGroup">Customer group</param>
    public virtual async Task DeleteCustomerGroup(CustomerGroup customerGroup)
    {
        ArgumentNullException.ThrowIfNull(customerGroup);

        if (customerGroup.IsSystem)
            throw new GrandException("System group could not be deleted");

        await _customerGroupRepository.DeleteAsync(customerGroup);

        //clear cache
        await _cacheBase.RemoveByPrefix(CacheKey.CUSTOMERGROUPS_PATTERN_KEY);

        //event notification
        await _mediator.EntityDeleted(customerGroup);
    }

    /// <summary>
    ///     Gets a value indicating whether customer is in a certain customer group
    /// </summary>
    /// <param name="customer">Customer</param>
    /// <param name="customerGroupSystemName">Customer group system name</param>
    /// <param name="onlyActiveCustomerGroups">A value indicating whether we should look only in active customer groups</param>
    /// <param name="isSystem">A value indicating whether we should look only in system groups</param>
    /// <returns>Result</returns>
    public virtual async Task<bool> IsInCustomerGroup(Customer customer,
        string customerGroupSystemName,
        bool onlyActiveCustomerGroups = true,
        bool? isSystem = null)
    {
        ArgumentNullException.ThrowIfNull(customer);

        if (string.IsNullOrEmpty(customerGroupSystemName))
            throw new ArgumentNullException(nameof(customerGroupSystemName));

        var customerGroup = await GetCustomerGroupBySystemName(customerGroupSystemName);
        if (customerGroup == null)
            return false;

        var result =
            customer.Groups.Contains(customerGroup.Id)
            && (!onlyActiveCustomerGroups || customerGroup.Active)
            && customerGroup.SystemName == customerGroupSystemName
            && (!isSystem.HasValue || customerGroup.IsSystem == isSystem);

        return result;
    }

    public Task<bool> IsStaff(Customer customer)
    {
        return IsInCustomerGroup(customer, SystemCustomerGroupNames.Staff, true, true);
    }

    public Task<bool> IsAdmin(Customer customer)
    {
        return IsInCustomerGroup(customer, SystemCustomerGroupNames.Administrators, true, true);
    }

    public Task<bool> IsSalesManager(Customer customer)
    {
        return IsInCustomerGroup(customer, SystemCustomerGroupNames.SalesManager, true, true);
    }

    public Task<bool> IsVendor(Customer customer)
    {
        return IsInCustomerGroup(customer, SystemCustomerGroupNames.Vendors, true, true);
    }

    public async Task<bool> IsOwner(Customer customer)
    {
        return await Task.FromResult(string.IsNullOrEmpty(customer.OwnerId));
    }

    public Task<bool> IsGuest(Customer customer)
    {
        return IsInCustomerGroup(customer, SystemCustomerGroupNames.Guests, true, true);
    }

    public Task<bool> IsRegistered(Customer customer)
    {
        return IsInCustomerGroup(customer, SystemCustomerGroupNames.Registered, true, true);
    }

    public async Task<IList<CustomerGroup>> GetAllByIds(string[] ids)
    {
        var customerGroups = await _cacheBase.GetAsync(CacheKey.CUSTOMERGROUPS_ALL, async () =>
        {
            var query = from cr in _customerGroupRepository.Table
                orderby cr.Name
                select cr;
            return await Task.FromResult(query.ToList());
        });
        return customerGroups.Where(x => ids.Contains(x.Id)).ToList();
    }
}